using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using EnvDTE80;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.VisualStudio.TeamFoundation.VersionControl;
using TeamReview.Services;
using TeamReview.Settings;
using System.Drawing;
using System.Threading;

namespace TeamReview.UI
{
    public partial class CreateWorkItemForm : Form
    {

        #region Variables

        private CodeSelection _codeSelection;
        private readonly DTE2 shellDTE;
        private Project workItemSubmissionProject;
        private readonly SettingsReaderWriter settings = new SettingsReaderWriter();
        private TeamSetting teamSetting;

        #endregion


        #region Constructor

        public CreateWorkItemForm(DTE2 shell, CodeSelection codeSelection)
        {
            InitializeComponent();
            shellDTE = shell;
            _codeSelection = codeSelection;
            RestoreWindow();
            TFSService.TeamProjectChanged += new TeamProjectChangeEventHandler(TFSService_TeamProjectChanged);

            //Binidng queries can kick off a number of threads to bind other
            //drop downs so we put it first in sequence for better percieved performance
            BindQueries();

            BindToolTips();
            BindTextBoxes();
        }

        #endregion

        #region Private "User Preferences" Methods (Settings)

        private void UpdateWindowSettings()
        {
            WindowSetting ws = settings.Get(this.Name);

            if (ws == null)
            {
                ws = new WindowSetting();
                settings.Add(ws);
            }

            ws.Name = this.Name;
            ws.Width = Width;
            ws.Height = Height;
            ws.WindowPoint = Location;
            ws.WindowState = WindowState;
        }

        private void UpdateTeamSettings()
        {

            TeamProject project = TFSService.GetTeamProject();
            TeamSetting ts = settings.Get(project);
            if (ts == null)
            {
                ts = new TeamSetting();
                ts.ArtifactUri = project.ArtifactUri.ToString();
                settings.Add(ts);
            }

            if (_cmbCodeItemType.SelectedItem != null)
                ts.CodeItemType = _cmbCodeItemType.SelectedItem.ToString();
            else
                ts.CodeItemType = String.Empty;

            if (_cmbLinkedItem.SelectedItem != null)
                ts.ItemAssociated = ((BindingItemCodeReview)_cmbLinkedItem.SelectedItem).Id;
            else
                ts.ItemAssociated = 0;

            if (_cmbQuery.SelectedItem != null)
                ts.QueryGuid = ((BindingItemQuery)_cmbQuery.SelectedItem).QueryGuid;
            else
                ts.QueryGuid = Guid.Empty;

            if (_cmbAssignedUser.SelectedItem != null)
                ts.UserAssigned = _cmbAssignedUser.SelectedItem.ToString();
            else
                ts.UserAssigned = String.Empty;

            if (_cmbPriority.SelectedItem != null)
                ts.Priority = _cmbPriority.SelectedItem.ToString();
            else
                ts.Priority = String.Empty;

            ts.ChangeSet = textBoxChangeset.Text;
        }

        private void RestoreWindow()
        {
            WindowSetting ws = settings.Get(this.Name);
            TeamProject project = TFSService.GetTeamProject();
            teamSetting = settings.Get(project);

            if (teamSetting == null)
            {
                teamSetting = new TeamSetting();
                teamSetting.ArtifactUri = project.ArtifactUri.ToString();
            }

            if (ws == null)
                return;

            this.StartPosition = FormStartPosition.Manual;
            Width = ws.Width;
            Height = ws.Height;
            Location = ws.WindowPoint;
            WindowState = ws.WindowState;
        }

        #endregion

        #region Event Handlers


        void TFSService_TeamProjectChanged()
        {
            Close();
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            Text = "New Code Item";
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            _errorProvider1.Clear();
            bool validUser = ValidateAssignedUser();
            bool validTitle = ValidateTitle();
            bool validDescription = ValidateDescription();
            bool validPriority = ValidatePriority();
            bool validCodeItemType = ValidateCodeItemType();
            if (!validUser || !validTitle || !validDescription || !validPriority || !validCodeItemType )
                return;

            Save();
            Close();
        }


        private void cmbQuery_SelectedIndexChanged(object sender, EventArgs e)
        {
            //Project quireied by the selected Query 
            Guid queryGuid = Guid.Empty;
            if (_cmbQuery.SelectedIndex >= 0)
                queryGuid = ((BindingItemQuery)_cmbQuery.SelectedItem).QueryGuid;

            if (queryGuid != Guid.Empty)
            {
     
                Project queryProject = TFSService.GetQueryProject(queryGuid);
                if (workItemSubmissionProject != queryProject)
                {
                    workItemSubmissionProject = queryProject;
                    BindUsers();
                    BindPriority();
                    BindCodeItemType();
                }
                BindLinkedItems();
            }
        }


        private void cmbLinkedItem_SelectedIndexChanged(object sender, EventArgs e)
        {

            //Project tied to Code Review work item that is selected
            WorkItem relatedCodeReview = GetRelatedCodeReview();
            if (relatedCodeReview != null)
            {
                if (workItemSubmissionProject != relatedCodeReview.Project)
                {
                    workItemSubmissionProject = relatedCodeReview.Project;
                    BindUsers();
                    BindPriority();
                }
            }
        }

        private void cmbAssignedUser_Validating(object sender, CancelEventArgs e)
        {
            ValidateAssignedUser();
        }

        private void txtTitle_Validating(object sender, CancelEventArgs e)
        {
            ValidateTitle();
        }


        private void txtDescription_Validating(object sender, CancelEventArgs e)
        {
            ValidateDescription();
        }


        private void textBoxChangeset_Click(object sender, EventArgs e)
        {
            this.SendToBack();
            Changeset cs = TFSService.VersionControlExt.FindChangeset();
            if (cs != null)
                textBoxChangeset.Text = cs.ChangesetId.ToString();

            this.BringToFront();
        }




        #endregion



        #region Data Binding

        private void BindLinkedItems()
        {
            _cmbLinkedItem.Text = String.Empty;
            _cmbLinkedItem.DataSource = null;
            if (_cmbQuery.SelectedItem != null)
            {
                Guid queryGuid = ((BindingItemQuery)_cmbQuery.SelectedItem).QueryGuid;
                BindingList<BindingItemCodeReview> bl = TFSService.GetAvailableQueryWorkItems(queryGuid);

                if (bl.Count == 0)
                    return;

                _cmbLinkedItem.DataSource = bl;

                for (int i = 0; i < bl.Count; i++)
                {
                    BindingItemCodeReview bicr = bl[i];
                    if (bicr.Id == teamSetting.ItemAssociated)
                        _cmbLinkedItem.SelectedIndex = i;
                }
            }
        }

        private void BindQueries()
        {
            _cmbQuery.Text = String.Empty;
            _cmbQuery.DataSource = null;
            BindingList<BindingItemQuery> bl = TFSService.GetAvailableTeamQueries();

            if (bl == null || bl.Count == 0)
                return;

            _cmbQuery.DataSource = bl;
            _cmbQuery.DisplayMember = "Name";

            for (int i = 0; i < bl.Count; i++)
            {
                if (bl[i].QueryGuid.Equals(teamSetting.QueryGuid))
                {
                    _cmbQuery.SelectedIndex = _cmbQuery.Items.IndexOf(bl[i]);
                    break;
                }
            }
        }

        private void BindUsers()
        {
            _cmbAssignedUser.Text = String.Empty;
            _cmbAssignedUser.Items.Clear();
            Project project = workItemSubmissionProject;

            List<string> users = TFSService.GetTeamUsers(project);
            if (users == null)
                return;

            _cmbAssignedUser.Items.AddRange(users.ToArray());
            if ((teamSetting != null) && (teamSetting.UserAssigned != null) && _cmbAssignedUser.Items.Count > 0)
                _cmbAssignedUser.SelectedIndex = _cmbAssignedUser.Items.IndexOf(teamSetting.UserAssigned);
        }

        private void BindPriority()
        {
            String settingValue = String.Empty;
            if (teamSetting != null)
                settingValue = teamSetting.Priority;
            BindOptionalDynamicField(Constants.CODE_PRIORITY_FIELD, "Priority", _cmbPriority, _lblPriority, settingValue);
        }

         private void BindCodeItemType()
        {
            String settingValue = String.Empty;
            if (teamSetting != null)
                settingValue = teamSetting.CodeItemType;
            BindOptionalDynamicField(Constants.CODE_ITEM_TYPE_FIELD, "Code Item Type", _cmbCodeItemType, _lblCodeItemType, settingValue);
        }

        private void BindOptionalDynamicField(string fieldName, string displayName, ComboBox cmb, Label lbl, String settingValue)
        {
            cmb.Text = String.Empty;
            cmb.DataSource = null;

            Project project = workItemSubmissionProject;
            string reason;
            WorkItemType wiType;
            if (!HasWorkItemDefinition(project, out reason, out wiType))
                return;

            FieldDefinitionCollection fields = wiType.FieldDefinitions;

            if (!fields.Contains(fieldName))
            {
                lbl.Font = new Font(lbl.Font, FontStyle.Regular);
                lbl.Refresh();
                Instrumentation.Trace(
                    String.Format("To use the {0} drop down update to the latest work item type from http://TeamReview.codeplex.com.", displayName));
                return;
            }

            AllowedValuesCollection ac = fields[fieldName].AllowedValues;
            if (ac != null)
                cmb.DataSource = ac;

            if (!String.IsNullOrEmpty(settingValue) && cmb.Items.Count > 0)
                cmb.SelectedIndex = cmb.Items.IndexOf(settingValue);

        }

        private void BindTextBoxes()
        {
            _txtSnippet.Text = _codeSelection.Snippet;
            if (String.IsNullOrEmpty(_codeSelection.CodeMember))
                _txtTitle.Text = String.Format("{0} {1}-{2}", _codeSelection.CodeFile, _codeSelection.StartLine, _codeSelection.EndLine);
            else
                _txtTitle.Text = String.Format("{0} {1}-{2}", _codeSelection.CodeMember, _codeSelection.StartLine, _codeSelection.EndLine);

            textBoxChangeset.Text = teamSetting.ChangeSet;

        }

        private void BindToolTips()
        {
            toolTip1.SetToolTip(_cmbQuery, "Select a Team Query to find a Work Item that will be linked to your new Code Item.");
            toolTip1.SetToolTip(_lblQueryFrom, toolTip1.GetToolTip(_cmbQuery));

            toolTip1.SetToolTip(_cmbLinkedItem, "Select a Work Item to link to your new Code Item.");
            toolTip1.SetToolTip(_lblAssociateWith, toolTip1.GetToolTip(_cmbLinkedItem));

            toolTip1.SetToolTip(_cmbAssignedUser, "Select a Coder'");
            toolTip1.SetToolTip(_lblAssignedUser, toolTip1.GetToolTip(_cmbAssignedUser));

            toolTip1.SetToolTip(_txtTitle, "Enter a title for your new Code Item.");
            toolTip1.SetToolTip(_lblTitle, toolTip1.GetToolTip(_txtTitle));

            toolTip1.SetToolTip(_txtDescription, "Enter why would you like the code to change.");
            toolTip1.SetToolTip(_lblDescription, toolTip1.GetToolTip(_txtDescription));

            toolTip1.SetToolTip(_txtResolution, "Enter how would you like the code to change.");
            toolTip1.SetToolTip(_lblResolution, toolTip1.GetToolTip(_txtResolution));
        }

        #endregion



        #region Data Validation

        private bool ValidateAssignedUser()
        {
            if (_cmbAssignedUser.SelectedItem != null)
                return true;

            if (!String.IsNullOrEmpty(_cmbAssignedUser.Text))
            {
                for (int i = 0; i < _cmbAssignedUser.Items.Count; i++)
                {
                    if (_cmbAssignedUser.Items[i].ToString().Equals(_cmbAssignedUser.Text))
                    {
                        _cmbAssignedUser.SelectedIndex = i;
                        return true;
                    }
                }
            }

            _errorProvider1.SetError(_lblAssignedUser, "You must assign a user to the new work item");
            return false;
        }


        private bool ValidatePriority()
        {
            if (_cmbPriority.Items.Count == 0 || _cmbPriority.SelectedIndex >= 0)
                return true;

            _errorProvider1.SetError(_lblPriority, "You must supply a Priority for the new work item");
            return false;
        }

        private bool ValidateCodeItemType()
        {
            if (_cmbCodeItemType.Items.Count == 0 || _cmbCodeItemType.SelectedIndex >= 0)
                return true;

            _errorProvider1.SetError(_lblCodeItemType, "You must supply a Code Item Type for the new work item");
            return false;
        }

        private bool ValidateTitle()
        {
            if (!String.IsNullOrEmpty(_txtTitle.Text))
                return true;

            _errorProvider1.SetError(_lblTitle, "You must supply a Title for the new work item");
            return false;
        }


        private bool ValidateDescription()
        {
            if (!String.IsNullOrEmpty(_txtDescription.Text))
                return true;

            _errorProvider1.SetError(_lblDescription, "You must supply a Description for the new work item");
            return false;
        }


        #endregion

        #region Work Item Submission

        private void AddChangesetLink(WorkItem workItem)
        {
            try
            {

                if (!string.IsNullOrEmpty(textBoxChangeset.Text))
                {
                    // Add a source code file linkage for the changeset to the workitem
                    RegisteredLinkTypeCollection linkTypes = TFSService.WorkItemStore.RegisteredLinkTypes;
                    RegisteredLinkType linkType = linkTypes["Source Code File"];
                    if (linkType != null)
                    {

                        Changeset reviewChangeset = TFSService.VersionControlServer.GetChangeset(Int32.Parse(textBoxChangeset.Text));
                        if (reviewChangeset != null)
                        {
                            ExternalLink changeLink = new ExternalLink(linkType, reviewChangeset.ArtifactUri.AbsoluteUri);
                            workItem.Links.Add(changeLink);
                        }

                    }
                }
            }
            catch (Exception e)
            {
                Instrumentation.Log(e);
            }
        }

        private void AddRelation(WorkItem workItem)
        {
            WorkItem relatedItem = GetRelatedCodeReview();

            if (relatedItem == null)
                return;

            workItem.Links.Add(new RelatedLink(relatedItem.Id));

            if (relatedItem.Fields[Constants.AREA_FIELD] != null)
                workItem.Fields[Constants.AREA_FIELD].Value = relatedItem.Fields[Constants.AREA_FIELD].Value;

            if (relatedItem.Fields[Constants.ITERATION_FIELD] != null)
                workItem.Fields[Constants.ITERATION_FIELD].Value = relatedItem.Fields[Constants.ITERATION_FIELD].Value;

        }


        private WorkItem CreateWorkItem(Project project)
        {
            string reason;
            WorkItemType wiType;
            if (!HasWorkItemDefinition(project, out reason, out wiType))
            {
                DirectionsForm directions = new DirectionsForm(reason);
                directions.ShowDialog();
                return null;
            }
           
            return wiType.NewWorkItem();
        }

        private void Save()
        {
            Project project = workItemSubmissionProject;

            //if (workItemSubmissionProject == null)
            //	project  = TFSService.WorkItemStore.Projects[TFSService.TeamProject.Name];

            WorkItem workItem = CreateWorkItem(project);
            if (workItem == null)
                return;

            try
            {
                workItem.Fields[Constants.CODE_DESCRIPTION_FIELD].Value = _txtDescription.Text;
                workItem.Fields[Constants.CODE_RESOLUTION_FIELD].Value = _txtResolution.Text;
                workItem.Fields[Constants.CODE_SNIPPET_FIELD].Value = _codeSelection.Snippet;
                workItem.Fields[Constants.CODE_FILE_FIELD].Value = _codeSelection.CodeFile;
                workItem.Fields[Constants.CODE_MEMBER_FIELD].Value = _codeSelection.CodeMember;
                workItem.Fields[Constants.CODE_PROJECT_FIELD].Value = _codeSelection.CodeProject;
                workItem.Fields[Constants.CODE_START_LINE_FIELD].Value = _codeSelection.StartLine.ToString();
                workItem.Fields[Constants.CODE_END_LINE_FIELD].Value = _codeSelection.EndLine.ToString();
                workItem.Fields[Constants.CODE_CLASS_FIELD].Value = _codeSelection.CodeClass;
                workItem.Fields[Constants.CODE_NAMESPACE_FIELD].Value = _codeSelection.Namespace;
                workItem.Fields[Constants.ASSIGNED_TO_FIELD].Value = _cmbAssignedUser.SelectedItem.ToString();
                workItem.Fields[Constants.TITLE_FIELD].Value = _txtTitle.Text;
                if (_cmbPriority.SelectedItem != null)
                    workItem.Fields[Constants.CODE_PRIORITY_FIELD].Value = _cmbPriority.SelectedItem;

                if (_cmbCodeItemType.SelectedItem != null)
                    workItem.Fields[Constants.CODE_ITEM_TYPE_FIELD].Value = _cmbCodeItemType.SelectedItem;

                AddChangesetLink(workItem);
                AddRelation(workItem);

                workItem.Save();

                string success = String.Format("#{0} created and assigned to {1} in Team Project {2}",
                                                 workItem.Id,
                                                 workItem.Fields[Constants.ASSIGNED_TO_FIELD].Value,
                                                 workItem.Project.Name);
                shellDTE.StatusBar.Text = success;
                Instrumentation.Trace(success);

                UpdateWindowSettings();
                UpdateTeamSettings();
                settings.Save();

            }
            catch (Exception e)
            {
                string message = "Field validation failed for the work item. Please file a defect with reproduceable steps at http://TeamReview.codeplex.com/";
                Instrumentation.Log(e);
                _errorProvider1.SetError(_lblBtnSave, message);
            }
        }

        #endregion


        private bool HasWorkItemDefinition(Project project, out string reason, out WorkItemType wiType)
        {
            reason = string.Empty;
            wiType = null;
            if (project.WorkItemTypes.Contains(Constants.CODE_ITEM_TYPE))
            {
                wiType = project.WorkItemTypes[Constants.CODE_ITEM_TYPE];
                return true;
            }

            reason = string.Empty;
            if (project.WorkItemTypes.Contains(Constants.CODE_REVIEW_ITEM_TYPE))
            {
                wiType = project.WorkItemTypes[Constants.CODE_REVIEW_ITEM_TYPE];
                return true;
            }


            reason = String.Format(
                    "The project '{0}' does not have the necessary TeamReview work item type. The '{0}' project may be used because it's where the Associated Item is stored or the source of the Query you selected",
                    project.Name);
            Instrumentation.Trace(reason);

             return false;
        }


        private WorkItem GetRelatedCodeReview()
        {
            if (_cmbLinkedItem.SelectedIndex < 0)
                return null;

            return TFSService.WorkItemStore.GetWorkItem(((BindingItemCodeReview)_cmbLinkedItem.SelectedItem).Id);
        }



    }
}